{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'\\n实验目的：实现基于CNN、RNN的文本分类\\n\\n实验内容：\\n1）词嵌入初始化方式：随机embedding、加载glove\\n2）CNN/RNN的特征抽取\\n3）Dropout\\n\\n\\n参考：\\nhttps://arxiv.org/abs/1408.5882\\nhttps://github.com/yokusama/CNN_Sentence_Classification\\nhttps://torchtext.readthedocs.io/en/latest/\\nhttp://mlexplained.com/2018/02/08/a-comprehensive-tutorial-to-torchtext/\\nhttps://machinelearningmastery.com/sequence-classification-lstm-recurrent-neural-networks-python-keras/\\nhttps://github.com/yunjey/pytorch-tutorial/blob/master/tutorials/02-intermediate/bidirectional_recurrent_neural_network/main.py#L39-L58\\n\\n'"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "'''\n",
    "实验目的：实现基于CNN、RNN的文本分类\n",
    "\n",
    "实验内容：\n",
    "1）词嵌入初始化方式：随机embedding、加载glove\n",
    "2）CNN/RNN的特征抽取\n",
    "3）Dropout\n",
    "\n",
    "\n",
    "参考：\n",
    "https://arxiv.org/abs/1408.5882\n",
    "https://github.com/yokusama/CNN_Sentence_Classification\n",
    "https://torchtext.readthedocs.io/en/latest/\n",
    "http://mlexplained.com/2018/02/08/a-comprehensive-tutorial-to-torchtext/\n",
    "https://machinelearningmastery.com/sequence-classification-lstm-recurrent-neural-networks-python-keras/\n",
    "https://github.com/yunjey/pytorch-tutorial/blob/master/tutorials/02-intermediate/bidirectional_recurrent_neural_network/main.py#L39-L58\n",
    "\n",
    "'''"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "D:\\workspace\\nlp-beginner_solution\\Task2\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "import time\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import sklearn\n",
    "print(os.getcwd())\n",
    "\n",
    "\n",
    "dir_all_data='data\\\\task2_all_data.tsv'\n",
    "\n",
    "#超参数设置\n",
    "BATCH_SIZE=10\n",
    "cpu=True   #True   False \n",
    "if cpu :\n",
    "    USE_CUDA = False\n",
    "    DEVICE = torch.device('cpu')\n",
    "else:\n",
    "    USE_CUDA = torch.cuda.is_available()\n",
    "    DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n",
    "    torch.cuda.set_device(0)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "#从文件中读取数据\n",
    "data_all=pd.read_csv(dir_all_data,sep='\\t')\n",
    "#print(all_data.shape)    #(156060, 4)\n",
    "#print(all_data.keys())   #['PhraseId', 'SentenceId', 'Phrase', 'Sentiment']\n",
    "idx =np.arange(data_all.shape[0])\n",
    "#print(data_all.head())\n",
    "#print(type(idx))   #<class 'numpy.ndarray'>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "#shuffle、划分验证集、测试集,并保存\n",
    "seed=0\n",
    "np.random.seed(seed)\n",
    "#print(idx)\n",
    "np.random.shuffle(idx)  \n",
    "#print(idx)\n",
    "\n",
    "train_size=int(len(idx) * 0.6)\n",
    "test_size =int(len(idx) * 0.8)\n",
    "\n",
    "data_all.iloc[idx[:train_size], :].to_csv('data/task2_train.csv',index=False)\n",
    "data_all.iloc[idx[train_size:test_size], :].to_csv(\"data/task2_test.csv\", index=False)\n",
    "data_all.iloc[idx[test_size:], :].to_csv(\"data/task2_dev.csv\", index=False)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "#使用Torchtext采用声明式方法加载数据\n",
    "from torchtext import data\n",
    "PAD_TOKEN='<pad>'\n",
    "TEXT = data.Field(sequential=True,batch_first=True, lower=True, pad_token=PAD_TOKEN)\n",
    "LABEL = data.Field(sequential=False, batch_first=True, unk_token=None)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "#读取数据\n",
    "datafields = [(\"PhraseId\", None), # 不需要的filed设置为None\n",
    "              (\"SentenceId\", None),\n",
    "              ('Phrase', TEXT),\n",
    "              ('Sentiment', LABEL)]\n",
    "train_data = data.TabularDataset(path='data/task2_train.csv', format='csv',\n",
    "                                fields=datafields)\n",
    "dev_data  = data.TabularDataset(path='data/task2_dev.csv', format='csv',\n",
    "                                fields=datafields)\n",
    "test_data = data.TabularDataset(path='data/task2_test.csv', format='csv',\n",
    "                                fields=datafields)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "#构建词典，字符映射到embedding\n",
    "#TEXT.vocab.vectors 就是词向量\n",
    "TEXT.build_vocab(train_data,  vectors= 'glove.6B.50d',   #可以提前下载好\n",
    "                 unk_init= lambda x:torch.nn.init.uniform_(x, a=-0.25, b=0.25))\n",
    "LABEL.build_vocab(train_data)\n",
    "\n",
    "\n",
    "#得到索引，PAD_TOKEN='<pad>'\n",
    "PAD_INDEX = TEXT.vocab.stoi[PAD_TOKEN]\n",
    "TEXT.vocab.vectors[PAD_INDEX] = 0.0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "#构建迭代器\n",
    "train_iterator = data.BucketIterator(train_data, batch_size=BATCH_SIZE, \n",
    "                                     train=True, shuffle=True,device=DEVICE)\n",
    "\n",
    "dev_iterator = data.Iterator(dev_data, batch_size=len(dev_data), train=False,\n",
    "                         sort=False, device=DEVICE)\n",
    "\n",
    "test_iterator = data.Iterator(test_data, batch_size=len(test_data), train=False,\n",
    "                          sort=False, device=DEVICE)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "16473 6\n"
     ]
    }
   ],
   "source": [
    "#部分参数设置\n",
    "embedding_choice='glove'   #  'static'    'non-static'\n",
    "num_embeddings = len(TEXT.vocab)\n",
    "embedding_dim =50\n",
    "dropout_p=0.5\n",
    "filters_num=100\n",
    "\n",
    "vocab_size=len(TEXT.vocab)\n",
    "label_num=len(LABEL.vocab)\n",
    "print(vocab_size,label_num)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch.nn.functional as F\n",
    "class CNN(nn.Module):\n",
    "    def __init__(self):\n",
    "        super(CNN,self).__init__()\n",
    "        self.embedding_choice=embedding_choice\n",
    "        \n",
    "        if self.embedding_choice==  'rand':\n",
    "            self.embedding=nn.Embedding(num_embeddings,embedding_dim)\n",
    "        if self.embedding_choice==  'glove':\n",
    "            self.embedding = nn.Embedding(num_embeddings, embedding_dim, \n",
    "                padding_idx=PAD_INDEX).from_pretrained(TEXT.vocab.vectors, freeze=True)\n",
    "            \n",
    "            \n",
    "        self.conv1 = nn.Conv2d(in_channels=1,out_channels=filters_num ,  #卷积产生的通道\n",
    "                               kernel_size=(3, embedding_dim), padding=(2,0))\n",
    "        \n",
    "        self.conv2 = nn.Conv2d(in_channels=1,out_channels=filters_num ,  #卷积产生的通道\n",
    "                               kernel_size=(4, embedding_dim), padding=(3,0))\n",
    "        \n",
    "        self.conv3 = nn.Conv2d(in_channels=1,out_channels=filters_num ,  #卷积产生的通道\n",
    "                               kernel_size=(5, embedding_dim), padding=(4,0))\n",
    "        \n",
    "        self.dropout = nn.Dropout(dropout_p)\n",
    "        \n",
    "        self.fc = nn.Linear(filters_num * 3, label_num)\n",
    "        \n",
    "    def forward(self,x):      # (Batch_size, Length) \n",
    "        x=self.embedding(x).unsqueeze(1)      #(Batch_size, Length, Dimention) \n",
    "                                       #(Batch_size, 1, Length, Dimention) \n",
    "        \n",
    "        x1 = F.relu(self.conv1(x)).squeeze(3)    #(Batch_size, filters_num, length+padding, 1) \n",
    "                                          #(Batch_size, filters_num, length+padding) \n",
    "        x1 = F.max_pool1d(x1, x1.size(2)).squeeze(2)  #(Batch_size, filters_num, 1)\n",
    "                                               #(Batch_size, filters_num) \n",
    "         \n",
    "        x2 = F.relu(self.conv2(x)).squeeze(3)  \n",
    "        x2 = F.max_pool1d(x2, x2.size(2)).squeeze(2)      \n",
    "        \n",
    "        x3 = F.relu(self.conv3(x)).squeeze(3)  \n",
    "        x3 = F.max_pool1d(x3, x3.size(2)).squeeze(2)      \n",
    "        \n",
    "        x = torch.cat((x1, x2, x3), dim=1)  #(Batch_size, filters_num *3 )\n",
    "        x = self.dropout(x)      #(Batch_size, filters_num *3 )\n",
    "        out = self.fc(x)       #(Batch_size, label_num  )\n",
    "        return out\n",
    "    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "#构建模型\n",
    "\n",
    "model=CNN()\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=0.001)#创建优化器SGD\n",
    "criterion = nn.CrossEntropyLoss()   #损失函数\n",
    "\n",
    "if USE_CUDA:\n",
    "    model.cuda()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 0_1.068%:  Training average Loss: 1.379272\n",
      "Epoch 0_2.136%:  Training average Loss: 1.314942\n",
      "Epoch 0_3.204%:  Training average Loss: 1.283542\n",
      "Epoch 0_4.272%:  Training average Loss: 1.256256\n",
      "Epoch 0_5.340%:  Training average Loss: 1.233009\n",
      "Epoch 0_6.408%:  Training average Loss: 1.213058\n",
      "Epoch 0_7.476%:  Training average Loss: 1.206474\n",
      "Epoch 0_8.544%:  Training average Loss: 1.191687\n",
      "Epoch 0_9.612%:  Training average Loss: 1.179150\n",
      "Epoch 0_10.680%:  Training average Loss: 1.174352\n",
      "Epoch 0_11.747%:  Training average Loss: 1.168913\n",
      "Epoch 0_12.815%:  Training average Loss: 1.157567\n",
      "Epoch 0_13.883%:  Training average Loss: 1.148696\n",
      "Epoch 0_14.951%:  Training average Loss: 1.142014\n",
      "Epoch 0_16.019%:  Training average Loss: 1.139952\n",
      "Epoch 0_17.087%:  Training average Loss: 1.137257\n",
      "Epoch 0_18.155%:  Training average Loss: 1.132205\n",
      "Epoch 0_19.223%:  Training average Loss: 1.128574\n",
      "Epoch 0_20.291%:  Training average Loss: 1.127181\n",
      "Epoch 0_21.359%:  Training average Loss: 1.125518\n",
      "Epoch 0_22.427%:  Training average Loss: 1.121572\n",
      "Epoch 0_23.495%:  Training average Loss: 1.117958\n",
      "Epoch 0_24.563%:  Training average Loss: 1.117635\n",
      "Epoch 0_25.631%:  Training average Loss: 1.114918\n",
      "Epoch 0_26.699%:  Training average Loss: 1.112991\n",
      "Epoch 0_27.767%:  Training average Loss: 1.111160\n",
      "Epoch 0_28.835%:  Training average Loss: 1.109056\n",
      "Epoch 0_29.903%:  Training average Loss: 1.107254\n",
      "Epoch 0_30.971%:  Training average Loss: 1.103808\n",
      "Epoch 0_32.039%:  Training average Loss: 1.101914\n",
      "Epoch 0_33.107%:  Training average Loss: 1.098473\n",
      "Epoch 0_34.175%:  Training average Loss: 1.097401\n",
      "Epoch 0_35.242%:  Training average Loss: 1.096112\n",
      "Epoch 0_36.310%:  Training average Loss: 1.095278\n",
      "Epoch 0_37.378%:  Training average Loss: 1.093694\n",
      "Epoch 0_38.446%:  Training average Loss: 1.093899\n",
      "Epoch 0_39.514%:  Training average Loss: 1.093925\n",
      "Epoch 0_40.582%:  Training average Loss: 1.092566\n",
      "Epoch 0_41.650%:  Training average Loss: 1.092824\n",
      "Epoch 0_42.718%:  Training average Loss: 1.091338\n",
      "Epoch 0_43.786%:  Training average Loss: 1.091077\n",
      "Epoch 0_44.854%:  Training average Loss: 1.089334\n",
      "Epoch 0_45.922%:  Training average Loss: 1.089281\n",
      "Epoch 0_46.990%:  Training average Loss: 1.089358\n",
      "Epoch 0_48.058%:  Training average Loss: 1.087649\n",
      "Epoch 0_49.126%:  Training average Loss: 1.087847\n",
      "Epoch 0_50.194%:  Training average Loss: 1.086999\n",
      "Epoch 0_51.262%:  Training average Loss: 1.085495\n",
      "Epoch 0_52.330%:  Training average Loss: 1.083852\n",
      "Epoch 0_53.398%:  Training average Loss: 1.082540\n",
      "Epoch 0_54.466%:  Training average Loss: 1.083195\n",
      "Epoch 0_55.534%:  Training average Loss: 1.082745\n",
      "Epoch 0_56.602%:  Training average Loss: 1.082017\n",
      "Epoch 0_57.670%:  Training average Loss: 1.081292\n",
      "Epoch 0_58.737%:  Training average Loss: 1.081155\n",
      "Epoch 0_59.805%:  Training average Loss: 1.080625\n",
      "Epoch 0_60.873%:  Training average Loss: 1.080676\n",
      "Epoch 0_61.941%:  Training average Loss: 1.080013\n",
      "Epoch 0_63.009%:  Training average Loss: 1.080090\n",
      "Epoch 0_64.077%:  Training average Loss: 1.080092\n",
      "Epoch 0_65.145%:  Training average Loss: 1.079584\n",
      "Epoch 0_66.213%:  Training average Loss: 1.079146\n",
      "Epoch 0_67.281%:  Training average Loss: 1.078675\n",
      "Epoch 0_68.349%:  Training average Loss: 1.077760\n",
      "Epoch 0_69.417%:  Training average Loss: 1.077302\n",
      "Epoch 0_70.485%:  Training average Loss: 1.077336\n",
      "Epoch 0_71.553%:  Training average Loss: 1.076855\n",
      "Epoch 0_72.621%:  Training average Loss: 1.076151\n",
      "Epoch 0_73.689%:  Training average Loss: 1.075679\n",
      "Epoch 0_74.757%:  Training average Loss: 1.075191\n",
      "Epoch 0_75.825%:  Training average Loss: 1.075051\n",
      "Epoch 0_76.893%:  Training average Loss: 1.074903\n",
      "Epoch 0_77.961%:  Training average Loss: 1.074643\n",
      "Epoch 0_79.029%:  Training average Loss: 1.074353\n",
      "Epoch 0_80.097%:  Training average Loss: 1.073574\n",
      "Epoch 0_81.164%:  Training average Loss: 1.073573\n",
      "Epoch 0_82.232%:  Training average Loss: 1.073266\n",
      "Epoch 0_83.300%:  Training average Loss: 1.073541\n",
      "Epoch 0_84.368%:  Training average Loss: 1.073257\n",
      "Epoch 0_85.436%:  Training average Loss: 1.073106\n",
      "Epoch 0_86.504%:  Training average Loss: 1.073458\n",
      "Epoch 0_87.572%:  Training average Loss: 1.073303\n",
      "Epoch 0_88.640%:  Training average Loss: 1.072322\n",
      "Epoch 0_89.708%:  Training average Loss: 1.072054\n",
      "Epoch 0_90.776%:  Training average Loss: 1.071693\n",
      "Epoch 0_91.844%:  Training average Loss: 1.071154\n",
      "Epoch 0_92.912%:  Training average Loss: 1.071021\n",
      "Epoch 0_93.980%:  Training average Loss: 1.070491\n",
      "Epoch 0_95.048%:  Training average Loss: 1.069539\n",
      "Epoch 0_96.116%:  Training average Loss: 1.069178\n",
      "Epoch 0_97.184%:  Training average Loss: 1.068993\n",
      "Epoch 0_98.252%:  Training average Loss: 1.068903\n",
      "Epoch 0_99.320%:  Training average Loss: 1.068580\n",
      "Epoch 0 :  Verification average Loss: 0.993862, Verification accuracy: 58.664659%,Total Time:156.033072\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\htfhxx\\.conda\\envs\\python36\\lib\\site-packages\\torch\\serialization.py:256: UserWarning: Couldn't retrieve source code for container of type CNN. It won't be checked for correctness upon loading.\n",
      "  \"type \" + obj.__name__ + \". It won't be checked \"\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model is saved in model_dict/model_glove/epoch_0_accuracy_0.586647\n"
     ]
    }
   ],
   "source": [
    "#开始训练\n",
    "import time\n",
    "epoch=100\n",
    "best_accuracy=0.0\n",
    "start_time=time.time()\n",
    "\n",
    "for i in range(epoch):\n",
    "    model.train()\n",
    "    total_loss=0.0\n",
    "    accuracy=0.0\n",
    "    total_correct=0.0\n",
    "    total_data_num = len(train_iterator.dataset)\n",
    "    steps = 0.0\n",
    "    #训练\n",
    "    for batch in train_iterator:\n",
    "        steps+=1\n",
    "        #print(steps)\n",
    "        optimizer.zero_grad() #  梯度缓存清零\n",
    "        \n",
    "        batch_text=batch.Phrase\n",
    "        batch_label=batch.Sentiment\n",
    "        out=model(batch_text)    #[batch_size, label_num]\n",
    "        loss = criterion(out, batch_label)\n",
    "        total_loss = total_loss + loss.item() \n",
    "\n",
    "        loss.backward()\n",
    "        optimizer.step()        \n",
    "\n",
    "        correct = (torch.max(out, dim=1)[1]  #get the indices\n",
    "                   .view(batch_label.size()) == batch_label).sum()\n",
    "        total_correct = total_correct + correct.item()\n",
    "\n",
    "        if steps%100==0:\n",
    "            print(\"Epoch %d_%.3f%%:  Training average Loss: %f\"\n",
    "                      %(i, steps * train_iterator.batch_size*100/len(train_iterator.dataset),total_loss/steps))  \n",
    "\n",
    "    #每个epoch都验证一下\n",
    "    model.eval()\n",
    "    total_loss=0.0\n",
    "    accuracy=0.0\n",
    "    total_correct=0.0\n",
    "    total_data_num = len(dev_iterator.dataset)\n",
    "    steps = 0.0    \n",
    "    for batch in dev_iterator:\n",
    "        steps+=1\n",
    "        batch_text=batch.Phrase\n",
    "        batch_label=batch.Sentiment\n",
    "        out=model(batch_text)\n",
    "        loss = criterion(out, batch_label)\n",
    "        total_loss = total_loss + loss.item()\n",
    "        \n",
    "        correct = (torch.max(out, dim=1)[1].view(batch_label.size()) == batch_label).sum()\n",
    "        total_correct = total_correct + correct.item()\n",
    "        \n",
    "        print(\"Epoch %d :  Verification average Loss: %f, Verification accuracy: %f%%,Total Time:%f\"\n",
    "          %(i, total_loss/steps, total_correct*100/total_data_num,time.time()-start_time))  \n",
    "        \n",
    "        if best_accuracy < total_correct/total_data_num :\n",
    "            best_accuracy =total_correct/total_data_num \n",
    "            torch.save(model,'model_dict/model_glove/epoch_%d_accuracy_%f'%(i,total_correct/total_data_num))\n",
    "            print('Model is saved in model_dict/model_glove/epoch_%d_accuracy_%f'%(i,total_correct/total_data_num))\n",
    "            #torch.cuda.empty_cache()\n",
    "    break #运行时去除break\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Test average Loss: 0.985207, Test accuracy: 0.196536，Total time: 29.704575\n"
     ]
    }
   ],
   "source": [
    "#测试-重新读取文件（方便重写成.py文件）\n",
    "PATH='model_dict/model_glove/epoch_0_accuracy_0.586647'\n",
    "model = torch.load(PATH)\n",
    "\n",
    "total_loss=0.0\n",
    "accuracy=0.0\n",
    "total_correct=0.0\n",
    "total_data_num = len(train_iterator.dataset)\n",
    "steps = 0.0    \n",
    "start_time=time.time()\n",
    "for batch in test_iterator:\n",
    "    steps+=1\n",
    "    batch_text=batch.Phrase\n",
    "    batch_label=batch.Sentiment\n",
    "    out=model(batch_text)\n",
    "    loss = criterion(out, batch_label)\n",
    "    total_loss = total_loss + loss.item()\n",
    "\n",
    "    correct = (torch.max(out, dim=1)[1].view(batch_label.size()) == batch_label).sum()\n",
    "    total_correct = total_correct + correct.item()\n",
    "    #break   \n",
    "\n",
    "print(\"Test average Loss: %f, Test accuracy: %f，Total time: %f\"\n",
    "  %(total_loss/steps, total_correct/total_data_num,time.time()-start_time) ) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "python36",
   "language": "python",
   "name": "python36"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
